// =====================================================================
// FreeImage Plugin Interface
//
// Design and implementation by
// - Floris van den Berg (floris@geekhq.nl)
// - Rui Lopes (ruiglopes@yahoo.com)
// - Detlev Vendt (detlev.vendt@brillit.de)
// - Petr Pytelka (pyta@lightcomp.com)
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// =====================================================================

#ifdef _MSC_VER
#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
#endif

#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <ctype.h>
#endif // _WIN32

#include "FreeImage.h"
#include "Utilities.h"
#include "FreeImageIO.h"
#include "Plugin.h"

#include "../Metadata/FreeImageTag.h"

// =====================================================================

using namespace std;

// =====================================================================
// Plugin search list
// =====================================================================

const char *
s_search_list[] = {
    "",
    "plugins\\",
};

static int s_search_list_size = sizeof(s_search_list) / sizeof(char *);
static PluginList *s_plugins = NULL;
static int s_plugin_reference_count = 0;


// =====================================================================
// Reimplementation of stricmp (it is not supported on some systems)
// =====================================================================

int
FreeImage_stricmp(const char *s1, const char *s2) {
    int c1, c2;

    do {
        c1 = tolower(*s1++);
        c2 = tolower(*s2++);
    } while (c1 && c1 == c2);

    return c1 - c2;
}

// =====================================================================
//  Implementation of PluginList
// =====================================================================

PluginList::PluginList() :
        m_plugin_map(),
        m_node_count(0) {
}

FREE_IMAGE_FORMAT
PluginList::AddNode(FI_InitProc init_proc, void *instance, const char *format, const char *description, const char *extension, const char *regexpr) {
    if (init_proc != NULL) {
        PluginNode *node = new(std::nothrow) PluginNode;
        Plugin *plugin = new(std::nothrow) Plugin;
        if(!node || !plugin) {
            if(node) delete node;
            if(plugin) delete plugin;
            FreeImage_OutputMessageProc(FIF_UNKNOWN, FI_MSG_ERROR_MEMORY);
            return FIF_UNKNOWN;
        }

        memset(plugin, 0, sizeof(Plugin));

        // fill-in the plugin structure
        // note we have memset to 0, so all unset pointers should be NULL)

        init_proc(plugin, (int)m_plugin_map.size());

        // get the format string (two possible ways)

        const char *the_format = NULL;

        if (format != NULL) {
            the_format = format;
        } else if (plugin->format_proc != NULL) {
            the_format = plugin->format_proc();
        }

        // add the node if it wasn't there already

        if (the_format != NULL) {
            node->m_id = (int)m_plugin_map.size();
            node->m_instance = instance;
            node->m_plugin = plugin;
            node->m_format = format;
            node->m_description = description;
            node->m_extension = extension;
            node->m_regexpr = regexpr;
            node->m_enabled = TRUE;

            m_plugin_map[(const int)m_plugin_map.size()] = node;

            return (FREE_IMAGE_FORMAT)node->m_id;
        }

        // something went wrong while allocating the plugin... cleanup

        delete plugin;
        delete node;
    }

    return FIF_UNKNOWN;
}

PluginNode *
PluginList::FindNodeFromFormat(const char *format) {
    for (map<int, PluginNode *>::iterator i = m_plugin_map.begin(); i != m_plugin_map.end(); ++i) {
        const char *the_format = ((*i).second->m_format != NULL) ? (*i).second->m_format : (*i).second->m_plugin->format_proc();

        if ((*i).second->m_enabled) {
            if (FreeImage_stricmp(the_format, format) == 0) {
                return (*i).second;
            }
        }
    }

    return NULL;
}

PluginNode *
PluginList::FindNodeFromMime(const char *mime) {
    for (map<int, PluginNode *>::iterator i = m_plugin_map.begin(); i != m_plugin_map.end(); ++i) {
        const char *the_mime = ((*i).second->m_plugin->mime_proc != NULL) ? (*i).second->m_plugin->mime_proc() : "";

        if ((*i).second->m_enabled) {
            if ((the_mime != NULL) && (strcmp(the_mime, mime) == 0)) {
                return (*i).second;
            }
        }
    }

    return NULL;
}

PluginNode *
PluginList::FindNodeFromFIF(int node_id) {
    map<int, PluginNode *>::iterator i = m_plugin_map.find(node_id);

    if (i != m_plugin_map.end()) {
        return (*i).second;
    }

    return NULL;
}

int
PluginList::Size() const {
    return (int)m_plugin_map.size();
}

BOOL
PluginList::IsEmpty() const {
    return m_plugin_map.empty();
}

PluginList::~PluginList() {
    for (map<int, PluginNode *>::iterator i = m_plugin_map.begin(); i != m_plugin_map.end(); ++i) {
#ifdef _WIN32
        if ((*i).second->m_instance != NULL) {
            FreeLibrary((HINSTANCE)(*i).second->m_instance);
        }
#endif
        delete (*i).second->m_plugin;
        delete ((*i).second);
    }
}

// =====================================================================
// Retrieve a pointer to the plugin list container
// =====================================================================

PluginList * DLL_CALLCONV
FreeImage_GetPluginList() {
    return s_plugins;
}

// =====================================================================
// Plugin System Initialization
// =====================================================================

void DLL_CALLCONV
FreeImage_Initialise(BOOL load_local_plugins_only) {
    if (s_plugin_reference_count++ == 0) {

        /*
          Note: initialize all singletons here
          in order to avoid race conditions with multi-threading
        */

        // initialise the TagLib singleton
        TagLib& s = TagLib::instance();

        // internal plugin initialization

        s_plugins = new(std::nothrow) PluginList;

        if (s_plugins) {
            /* NOTE :
               The order used to initialize internal plugins below MUST BE the same order
               as the one used to define the FREE_IMAGE_FORMAT enum.
            */
            s_plugins->AddNode(InitBMP);
            s_plugins->AddNode(InitICO);
            s_plugins->AddNode(InitJPEG);
            s_plugins->AddNode(InitJNG);
            s_plugins->AddNode(InitKOALA);
            s_plugins->AddNode(InitIFF);
            s_plugins->AddNode(InitMNG);
            s_plugins->AddNode(InitPNM, NULL, "PBM", "Portable Bitmap (ASCII)", "pbm", "^P1");
            s_plugins->AddNode(InitPNM, NULL, "PBMRAW", "Portable Bitmap (RAW)", "pbm", "^P4");
            s_plugins->AddNode(InitPCD);
            s_plugins->AddNode(InitPCX);
            s_plugins->AddNode(InitPNM, NULL, "PGM", "Portable Greymap (ASCII)", "pgm", "^P2");
            s_plugins->AddNode(InitPNM, NULL, "PGMRAW", "Portable Greymap (RAW)", "pgm", "^P5");
            s_plugins->AddNode(InitPNG);
            s_plugins->AddNode(InitPNM, NULL, "PPM", "Portable Pixelmap (ASCII)", "ppm", "^P3");
            s_plugins->AddNode(InitPNM, NULL, "PPMRAW", "Portable Pixelmap (RAW)", "ppm", "^P6");
            s_plugins->AddNode(InitRAS);
            s_plugins->AddNode(InitTARGA);
            s_plugins->AddNode(InitTIFF);
            s_plugins->AddNode(InitWBMP);
            s_plugins->AddNode(InitPSD);
            s_plugins->AddNode(InitCUT);
            s_plugins->AddNode(InitXBM);
            s_plugins->AddNode(InitXPM);
            s_plugins->AddNode(InitDDS);
            s_plugins->AddNode(InitGIF);
            s_plugins->AddNode(InitHDR);
            s_plugins->AddNode(InitG3);
            s_plugins->AddNode(InitSGI);
            s_plugins->AddNode(InitEXR);
            s_plugins->AddNode(InitJ2K);
            s_plugins->AddNode(InitJP2);
            s_plugins->AddNode(InitPFM);
            s_plugins->AddNode(InitPICT);
            s_plugins->AddNode(InitRAW);
            s_plugins->AddNode(InitWEBP);
#if !(defined(_MSC_VER) && (_MSC_VER <= 1310))
            s_plugins->AddNode(InitJXR);
#endif // unsupported by MS Visual Studio 2003 !!!

            // external plugin initialization

#ifdef _WIN32
            if (!load_local_plugins_only) {
                int count = 0;
                char buffer[MAX_PATH + 200];
                wchar_t current_dir[2 * _MAX_PATH], module[2 * _MAX_PATH];
                BOOL bOk = FALSE;

                // store the current directory. then set the directory to the application location

                if (GetCurrentDirectoryW(2 * _MAX_PATH, current_dir) != 0) {
                    if (GetModuleFileNameW(NULL, module, 2 * _MAX_PATH) != 0) {
                        wchar_t *last_point = wcsrchr(module, L'\\');

                        if (last_point) {
                            *last_point = L'\0';

                            bOk = SetCurrentDirectoryW(module);
                        }
                    }
                }

                // search for plugins

                while (count < s_search_list_size) {
                    _finddata_t find_data;
                    long find_handle;

                    strcpy(buffer, s_search_list[count]);
                    strcat(buffer, "*.fip");

                    if ((find_handle = (long)_findfirst(buffer, &find_data)) != -1L) {
                        do {
                            strcpy(buffer, s_search_list[count]);
                            strncat(buffer, find_data.name, MAX_PATH + 200);

                            HINSTANCE instance = LoadLibrary(buffer);

                            if (instance != NULL) {
                                FARPROC proc_address = GetProcAddress(instance, "_Init@8");

                                if (proc_address != NULL) {
                                    s_plugins->AddNode((FI_InitProc)proc_address, (void *)instance);
                                } else {
                                    FreeLibrary(instance);
                                }
                            }
                        } while (_findnext(find_handle, &find_data) != -1L);

                        _findclose(find_handle);
                    }

                    count++;
                }

                // restore the current directory

                if (bOk) {
                    SetCurrentDirectoryW(current_dir);
                }
            }
#endif // _WIN32
        }
    }
}

void DLL_CALLCONV
FreeImage_DeInitialise() {
    --s_plugin_reference_count;

    if (s_plugin_reference_count == 0) {
        delete s_plugins;
    }
}

// =====================================================================
// Open and close a bitmap
// =====================================================================

void * DLL_CALLCONV
FreeImage_Open(PluginNode *node, FreeImageIO *io, fi_handle handle, BOOL open_for_reading) {
    if (node->m_plugin->open_proc != NULL) {
        return node->m_plugin->open_proc(io, handle, open_for_reading);
    }

    return NULL;
}

void DLL_CALLCONV
FreeImage_Close(PluginNode *node, FreeImageIO *io, fi_handle handle, void *data) {
    if (node->m_plugin->close_proc != NULL) {
        node->m_plugin->close_proc(io, handle, data);
    }
}

// =====================================================================
// Plugin System Load/Save Functions
// =====================================================================

FIBITMAP * DLL_CALLCONV
FreeImage_LoadFromHandle(FREE_IMAGE_FORMAT fif, FreeImageIO *io, fi_handle handle, int flags) {
    if ((fif >= 0) && (fif < FreeImage_GetFIFCount())) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        if (node != NULL) {
            if(node->m_plugin->load_proc != NULL) {
                void *data = FreeImage_Open(node, io, handle, TRUE);

                FIBITMAP *bitmap = node->m_plugin->load_proc(io, handle, -1, flags, data);

                FreeImage_Close(node, io, handle, data);

                return bitmap;
            }
        }
    }

    return NULL;
}

FIBITMAP * DLL_CALLCONV
FreeImage_Load(FREE_IMAGE_FORMAT fif, const char *filename, int flags) {
    FreeImageIO io;
    SetDefaultIO(&io);

    FILE *handle = fopen(filename, "rb");

    if (handle) {
        FIBITMAP *bitmap = FreeImage_LoadFromHandle(fif, &io, (fi_handle)handle, flags);

        fclose(handle);

        return bitmap;
    } else {
        FreeImage_OutputMessageProc((int)fif, "FreeImage_Load: failed to open file %s", filename);
    }

    return NULL;
}

FIBITMAP * DLL_CALLCONV
FreeImage_LoadU(FREE_IMAGE_FORMAT fif, const wchar_t *filename, int flags) {
    FreeImageIO io;
    SetDefaultIO(&io);
#ifdef _WIN32
    FILE *handle = _wfopen(filename, L"rb");

    if (handle) {
        FIBITMAP *bitmap = FreeImage_LoadFromHandle(fif, &io, (fi_handle)handle, flags);

        fclose(handle);

        return bitmap;
    } else {
        FreeImage_OutputMessageProc((int)fif, "FreeImage_LoadU: failed to open input file");
    }
#endif
    return NULL;
}

BOOL DLL_CALLCONV
FreeImage_SaveToHandle(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, FreeImageIO *io, fi_handle handle, int flags) {
    // cannot save "header only" formats
    if(FreeImage_HasPixels(dib) == FALSE) {
        FreeImage_OutputMessageProc((int)fif, "FreeImage_SaveToHandle: cannot save \"header only\" formats");
        return FALSE;
    }

    if ((fif >= 0) && (fif < FreeImage_GetFIFCount())) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        if (node) {
            if(node->m_plugin->save_proc != NULL) {
                void *data = FreeImage_Open(node, io, handle, FALSE);

                BOOL result = node->m_plugin->save_proc(io, dib, handle, -1, flags, data);

                FreeImage_Close(node, io, handle, data);

                return result;
            }
        }
    }

    return FALSE;
}


BOOL DLL_CALLCONV
FreeImage_Save(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const char *filename, int flags) {
    FreeImageIO io;
    SetDefaultIO(&io);

    FILE *handle = fopen(filename, "w+b");

    if (handle) {
        BOOL success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);

        fclose(handle);

        return success;
    } else {
        FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to open file %s", filename);
    }

    return FALSE;
}

BOOL DLL_CALLCONV
FreeImage_SaveU(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const wchar_t *filename, int flags) {
    FreeImageIO io;
    SetDefaultIO(&io);
#ifdef _WIN32
    FILE *handle = _wfopen(filename, L"w+b");

    if (handle) {
        BOOL success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);

        fclose(handle);

        return success;
    } else {
        FreeImage_OutputMessageProc((int)fif, "FreeImage_SaveU: failed to open output file");
    }
#endif
    return FALSE;
}

// =====================================================================
// Plugin construction + enable/disable functions
// =====================================================================

FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_RegisterLocalPlugin(FI_InitProc proc_address, const char *format, const char *description, const char *extension, const char *regexpr) {
    return s_plugins->AddNode(proc_address, NULL, format, description, extension, regexpr);
}

#ifdef _WIN32
FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_RegisterExternalPlugin(const char *path, const char *format, const char *description, const char *extension, const char *regexpr) {
    if (path != NULL) {
        HINSTANCE instance = LoadLibrary(path);

        if (instance != NULL) {
            FARPROC proc_address = GetProcAddress(instance, "_Init@8");

            FREE_IMAGE_FORMAT result = s_plugins->AddNode((FI_InitProc)proc_address, (void *)instance, format, description, extension, regexpr);

            if (result == FIF_UNKNOWN)
                FreeLibrary(instance);

            return result;
        }
    }

    return FIF_UNKNOWN;
}
#endif // _WIN32

int DLL_CALLCONV
FreeImage_SetPluginEnabled(FREE_IMAGE_FORMAT fif, BOOL enable) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        if (node != NULL) {
            BOOL previous_state = node->m_enabled;

            node->m_enabled = enable;

            return previous_state;
        }
    }

    return -1;
}

int DLL_CALLCONV
FreeImage_IsPluginEnabled(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? node->m_enabled : FALSE;
    }

    return -1;
}

// =====================================================================
// Plugin Access Functions
// =====================================================================

int DLL_CALLCONV
FreeImage_GetFIFCount() {
    return (s_plugins != NULL) ? s_plugins->Size() : 0;
}

FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_GetFIFFromFormat(const char *format) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFormat(format);

        return (node != NULL) ? (FREE_IMAGE_FORMAT)node->m_id : FIF_UNKNOWN;
    }

    return FIF_UNKNOWN;
}

FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_GetFIFFromMime(const char *mime) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromMime(mime);

        return (node != NULL) ? (FREE_IMAGE_FORMAT)node->m_id : FIF_UNKNOWN;
    }

    return FIF_UNKNOWN;
}

const char * DLL_CALLCONV
FreeImage_GetFormatFromFIF(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? (node->m_format != NULL) ? node->m_format : node->m_plugin->format_proc() : NULL;
    }

    return NULL;
}

const char * DLL_CALLCONV
FreeImage_GetFIFMimeType(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? (node->m_plugin != NULL) ? ( node->m_plugin->mime_proc != NULL )? node->m_plugin->mime_proc() : NULL : NULL : NULL;
    }

    return NULL;
}

const char * DLL_CALLCONV
FreeImage_GetFIFExtensionList(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? (node->m_extension != NULL) ? node->m_extension : (node->m_plugin->extension_proc != NULL) ? node->m_plugin->extension_proc() : NULL : NULL;
    }

    return NULL;
}

const char * DLL_CALLCONV
FreeImage_GetFIFDescription(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? (node->m_description != NULL) ? node->m_description : (node->m_plugin->description_proc != NULL) ? node->m_plugin->description_proc() : NULL : NULL;
    }

    return NULL;
}

const char * DLL_CALLCONV
FreeImage_GetFIFRegExpr(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? (node->m_regexpr != NULL) ? node->m_regexpr : (node->m_plugin->regexpr_proc != NULL) ? node->m_plugin->regexpr_proc() : NULL : NULL;
    }

    return NULL;
}

BOOL DLL_CALLCONV
FreeImage_FIFSupportsReading(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? node->m_plugin->load_proc != NULL : FALSE;
    }

    return FALSE;
}

BOOL DLL_CALLCONV
FreeImage_FIFSupportsWriting(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ? node->m_plugin->save_proc != NULL : FALSE ;
    }

    return FALSE;
}

BOOL DLL_CALLCONV
FreeImage_FIFSupportsExportBPP(FREE_IMAGE_FORMAT fif, int depth) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ?
                (node->m_plugin->supports_export_bpp_proc != NULL) ?
                node->m_plugin->supports_export_bpp_proc(depth) : FALSE : FALSE;
    }

    return FALSE;
}

BOOL DLL_CALLCONV
FreeImage_FIFSupportsExportType(FREE_IMAGE_FORMAT fif, FREE_IMAGE_TYPE type) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ?
                (node->m_plugin->supports_export_type_proc != NULL) ?
                node->m_plugin->supports_export_type_proc(type) : FALSE : FALSE;
    }

    return FALSE;
}

BOOL DLL_CALLCONV
FreeImage_FIFSupportsICCProfiles(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ?
                (node->m_plugin->supports_icc_profiles_proc != NULL) ?
                node->m_plugin->supports_icc_profiles_proc() : FALSE : FALSE;
    }

    return FALSE;
}

BOOL DLL_CALLCONV
FreeImage_FIFSupportsNoPixels(FREE_IMAGE_FORMAT fif) {
    if (s_plugins != NULL) {
        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        return (node != NULL) ?
                (node->m_plugin->supports_no_pixels_proc != NULL) ?
                node->m_plugin->supports_no_pixels_proc() : FALSE : FALSE;
    }

    return FALSE;
}

FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_GetFIFFromFilename(const char *filename) {
    if (filename != NULL) {
        const char *extension;

        // get the proper extension if we received a filename

        char *place = strrchr((char *)filename, '.');
        extension = (place != NULL) ? ++place : filename;

        // look for the extension in the plugin table

        for (int i = 0; i < FreeImage_GetFIFCount(); ++i) {

            if (s_plugins->FindNodeFromFIF(i)->m_enabled) {

                // compare the format id with the extension

                if (FreeImage_stricmp(FreeImage_GetFormatFromFIF((FREE_IMAGE_FORMAT)i), extension) == 0) {
                    return (FREE_IMAGE_FORMAT)i;
                } else {
                    // make a copy of the extension list and split it

                    char *copy = (char *)malloc(strlen(FreeImage_GetFIFExtensionList((FREE_IMAGE_FORMAT)i)) + 1);
                    memset(copy, 0, strlen(FreeImage_GetFIFExtensionList((FREE_IMAGE_FORMAT)i)) + 1);
                    memcpy(copy, FreeImage_GetFIFExtensionList((FREE_IMAGE_FORMAT)i), strlen(FreeImage_GetFIFExtensionList((FREE_IMAGE_FORMAT)i)));

                    // get the first token

                    char *token = strtok(copy, ",");

                    while (token != NULL) {
                        if (FreeImage_stricmp(token, extension) == 0) {
                            free(copy);

                            return (FREE_IMAGE_FORMAT)i;
                        }

                        token = strtok(NULL, ",");
                    }

                    // free the copy of the extension list

                    free(copy);
                }
            }
        }
    }

    return FIF_UNKNOWN;
}

FREE_IMAGE_FORMAT DLL_CALLCONV
FreeImage_GetFIFFromFilenameU(const wchar_t *filename) {
#ifdef _WIN32
    if (filename == NULL) return FIF_UNKNOWN;

    // get the proper extension if we received a filename
    wchar_t *place = wcsrchr((wchar_t *)filename, '.');
    if (place == NULL) return FIF_UNKNOWN;
    // convert to single character - no national chars in extensions
    char *extension = (char *)malloc(wcslen(place)+1);
    unsigned int i=0;
    for(; i < wcslen(place); i++) // convert 16-bit to 8-bit
        extension[i] = (char)(place[i] & 0x00FF);
    // set terminating 0
    extension[i]=0;
    FREE_IMAGE_FORMAT fRet = FreeImage_GetFIFFromFilename(extension);
    free(extension);

    return fRet;
#else
    return FIF_UNKNOWN;
#endif // _WIN32
}

BOOL DLL_CALLCONV
FreeImage_Validate(FREE_IMAGE_FORMAT fif, FreeImageIO *io, fi_handle handle) {
    if (s_plugins != NULL) {
        BOOL validated = FALSE;

        PluginNode *node = s_plugins->FindNodeFromFIF(fif);

        if (node) {
            long tell = io->tell_proc(handle);

            validated = (node != NULL) ? (node->m_enabled) ? (node->m_plugin->validate_proc != NULL) ? node->m_plugin->validate_proc(io, handle) : FALSE : FALSE : FALSE;

            io->seek_proc(handle, tell, SEEK_SET);
        }

        return validated;
    }

    return FALSE;
}
